/*    This file is part of ilk.
 *
 *    Ilk is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    Ilk is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with ilk.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dirent.h>
#include <fcntl.h>
#include <inttypes.h>
#include <rid.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define STYLE_9
#include <fn85.h>
#include <rid_fn85.h>

#include <unfy/unfy.h>
#include <rls/rls.h>
#include <rls/rlsp.h>

#define NIT_SHORT_NAMES
#include <nit/list.h>
#include <nit/set.h>
#include <nit/map.h>
#include <nit/err.h>
#include <nit/file.h>
#include <nit/buf.h>
#include <nit/gap.h>

#include "utilize.h"

#include "examine.h"

void
ilk_rule_path(Ilk_rule_path path, const Rid id)
{
        memcpy(path, "rules/", sizeof("rules/") - 1);
	rid_fn85_path(path + sizeof("rules/") - 1, id);
}

void
ilk_meta_path(Ilk_meta_path path, const Rid id)
{
	memcpy(path, "metas/", sizeof("metas/") - 1);
	rid_fn85_path(path + sizeof("metas/") - 1, id);
}

void
ilk_srch_path(Ilk_srch_path path, uint64_t srch_cnt)
{
	char num_str[21];
	/* 6      + 2         + 1 + 20     +  9                   + 1 = 38 */
	/* srchs/ + digit num + / + digits + / for every 2 digits + \0 */
	/* this is a maximum, it should be less than this most of the time */
	/* srchs/20/18/44/67/44/07/37/09/55/16/15 is max possible */
	size_t len;
	int i = 0;
	int even;

	/* convert to string */
	sprintf(num_str, "%" PRIu64, srch_cnt);
	memcpy(path, "srchs/", sizeof("srchs/") - 1);
	i += sizeof("srchs/") - 1;
	len = strlen(num_str);
	sprintf(path + i, "%zu", len);
	i += (len >= 10) ? 2 : 1;
	even = (len + 1) % 2;
	path[i++] = '/';

	for (int j = 0; num_str[j]; ++j) {
		path[i++] = num_str[j];

		/* makes sure 2 digits at end if two or more */
		if (!((j + even) % 2) && num_str[j + 1])
			path[i++] = '/';
	}

	path[i] = '\0';
}

static int
path_content(Ilk_sys *sys, const char *path, size_t *len)
{
	Nit_file file;
	Nit_err tmp;

	switch (file_exists(&sys->dir, path, &sys->lib->err)) {
	case 0:
		err_cust(&sys->lib->err, "File does not exist");
		err_where(&sys->lib->err, path);
		return -1;
	case 1:
		break;
	default:
	        return -1;
	}

	if (file_open_mode(&file, &sys->dir, path,
			   O_RDONLY, &sys->lib->err) < 0)
		return -1;

	if (!file.len) {
		err_cust(&sys->lib->err, "Empty file");
		err_where(&sys->lib->err, path);
		goto err;
	}

	if (file_content(&file, &sys->lib->buf.bytes,
			 &sys->lib->buf.size, &sys->lib->err) < 0)
		goto err;

	*len = file.len;

	if (file_close(&file, &sys->lib->err) < 0)
		goto err;

	return 0;
err:
        file_close(&file, &tmp); /* There's already an error */
	return -1;
}

int
ilk_path_grp(Ilk_grp *grp, const char *path, int check, Ilk_sys *sys)
{
	const char *error;
	size_t len;
	size_t pos = 0;

	if (path_content(sys, path, &len) < 0)
		return -1;

	if (len < sizeof(Rid)) {
		err_cust(&sys->lib->err, "Not enough space for id");
		goto err;
	}

	if (check) {
		if (rid_cmp(grp->id, sys->lib->buf.bytes)) {
			err_cust(&sys->lib->err, "Differing id for grp");
			goto err;
		}
	} else {
		rid_set(grp->id, sys->lib->buf.bytes);
	}

	pos = sizeof(Rid);

	if ((error = rlsp_rule_deserable(sys->lib->buf.bytes + pos, &pos,
					 len, &sys->lib->srlz, NULL))) {
		err_cust(&sys->lib->err, error);
		goto err;
	}

        pos = sizeof(Rid);

	if (rlsp_rule_deser(&grp->rules, sys->lib->buf.bytes + pos, &pos,
			    &sys->lib->srlz, sys, &sys->lib->rec) < 0) {
		err_cust(&sys->lib->err, "Failed to deserialize rules");
		goto err;
	}

	grp->accessed = 1;
	grp->changed = 0;
	return 0;
err:
	err_where(&sys->lib->err, path);
	err_pos(&sys->lib->err, pos);
	return -1;
}

static void
more_free(void *dat, void *extra)
{
	Ilk_more *more = dat;
	Ilk_sys *sys = extra;

	*(Ilk_more **) more = sys->lib->old_mores;
	sys->lib->old_mores = more;
}

void
ilk_grp_dispose(Ilk_grp *grp, Ilk_sys *sys)
{
	rls_recycle_rule(&sys->lib->rec, grp->rules, more_free, sys);
}

int
ilk_path_meta(Ilk_meta *meta, const char *path, int check, Ilk_sys *sys)
{
	const char *error;
	size_t len;
	size_t pos = 0;

	if (path_content(sys, path, &len) < 0)
		return -1;

	if (len < sizeof(Rid)) {
		err_cust(&sys->lib->err, "Not enough space for id");
		goto err;
	}

	if (check && rid_cmp(meta->id, sys->lib->buf.bytes)) {
		err_cust(&sys->lib->err, "Differing id for grp");
		goto err;
	}

	if ((error = ilk_meta_deserable(sys->lib->buf.bytes, &pos, len))) {
		err_cust(&sys->lib->err, error);
		goto err;
	}

        pos = 0;

	if (ilk_meta_deser(meta, sys->lib->buf.bytes, &pos) < 0) {
		err_cust(&sys->lib->err, "Failed to deserialize meta");
		goto err;
	}

	return 0;
err:
	err_where(&sys->lib->err, path);
	err_pos(&sys->lib->err, pos);
	return -1;
}

int
ilk_path_srch(Rls_srch **srch_ref, const char *path, Ilk_sys *sys)
{
	const char *error;
	size_t len;
	size_t pos = 0;
	unsigned char *buf;
	size_t size;

	if (path_content(sys, path, &len) < 0)
		return -1;

	if ((error = rlsp_srch_deserable(sys->lib->buf.bytes, &pos, len))) {
		err_cust(&sys->lib->err, error);
		goto err;
	}

        pos = 0;
	buf = sys->lib->buf.bytes;
	/* swap buffer since it can be overwritten */
	sys->lib->buf.bytes = NULL;
	size = sys->lib->buf.size;
	sys->lib->buf.size = 0;

	if ((error = rlsp_srch_deser(srch_ref, buf, &pos, NULL,
				     sys->lib->bend, sys, &sys->lib->rec))) {
		/* unswap buffer */
		free(sys->lib->buf.bytes);
		sys->lib->buf.bytes = buf;
		sys->lib->buf.size = size;

		if (sys->lib->err.occured)
			return -1;

		err_cust(&sys->lib->err, error);
		goto err;
	}

	/* unswap buffer */
	free(sys->lib->buf.bytes);
	sys->lib->buf.bytes = buf;
	sys->lib->buf.size = size;
	return 0;
err:
	err_where(&sys->lib->err, path);
	err_pos(&sys->lib->err, pos);
	return -1;
}

int
ilk_path_meta_dep(Ilk_sys *sys, const char *path, uint64_t srch_cnt)
{
	Nit_file file;
	uint64_t deps_len;
	Nit_err tmp;

	switch (file_exists(&sys->dir, path, &sys->lib->err)) {
	case 1:
		break;
	case 0:
		err_cust(&sys->lib->err, "No file found for meta");
		/* fallthru */
	default:
		/* error without file */
		goto err_no_file;
	}

	if (file_open(&file, &sys->dir, path, &sys->lib->err))
		goto err_no_file;

	if ((size_t) file.len < 2 * sizeof(Rid) + 2 * sizeof(uint64_t)) {
		err_cust(&sys->lib->err, "File too small for meta");
		goto err_file;
	}

	if (file_pos(&file, 2 * sizeof(Rid) + sizeof(uint64_t),
		     &sys->lib->err) < 0)
		goto err_file;

	if (file_read(&file, &deps_len, sizeof(deps_len), &sys->lib->err) < 0)
		goto err_file;

	deps_len = le64toh(deps_len);
	deps_len += sizeof(srch_cnt);
	deps_len = htole64(deps_len);

	if (file_pos(&file, 2 * sizeof(Rid) + sizeof(uint64_t),
		     &sys->lib->err) < 0)
		goto err_file;

	if (file_write(&file, &deps_len, sizeof(deps_len), &sys->lib->err) < 0)
		goto err_file;

	file_end(&file);

	if (file_write(&file, &srch_cnt, sizeof(srch_cnt), &sys->lib->err) < 0)
		goto err_file;

	if (file_close(&file, &sys->lib->err) < 0)
		goto err_file;

	return 0;
err_file:
	file_close(&file, &tmp); /* There's already an error */
err_no_file:
	err_where(&sys->lib->err, path);
	return -1;
}

int
ilk_meta_ser(Ilk_meta *meta, unsigned char **buf_ref,
	     size_t *size, size_t *pos)
{
	uint64_t deps_len = gap_len(&meta->deps);
	size_t len = 2 * sizeof(Rid) + 2 * sizeof(uint64_t) +
		deps_len;
	unsigned char *buf = *buf_ref;

	if (len > *size) {
		if (!(buf = realloc(buf, len)))
			return -1;

		*size = len;
	        *buf_ref = buf;
	}

	rid_set(buf, meta->id);
	buf += sizeof(Rid);
	rid_set(buf, meta->grp_id);
	buf += sizeof(Rid);
	*(uint64_t *) buf = htole64(meta->srch_cnt);
	buf += sizeof(uint64_t);
	*(uint64_t *) buf = htole64(deps_len);
	buf += sizeof(uint64_t);
	gap_read(&meta->deps, buf, deps_len);
	*pos = len;
	return 0;
}

const char *
ilk_meta_deserable(const unsigned char *buf, size_t *pos,
		   const size_t max)
{
	uint64_t deps_len;

	if (!buf)
		return "Not enough space for meta";

	if (*pos + 2 * sizeof(Rid) + 2 * sizeof(uint64_t) > max) {
		*pos = max;
		return "Not enough space for meta";
	}

	buf += 2 * sizeof(Rid) + sizeof(uint64_t);
	deps_len = le64toh(*(uint64_t *) buf);
	*pos += 2 * sizeof(Rid) + 2 * sizeof(uint64_t);

	if ((*pos += deps_len) > max)
		return "Deps len for meta too large";

	return NULL;
}

int
ilk_meta_deser(Ilk_meta *meta, const unsigned char *buf, size_t *pos)
{
	uint64_t deps_len;

	rid_set(meta->id, buf);
	buf += sizeof(Rid);
	rid_set(meta->grp_id, buf);
	buf += sizeof(Rid);
	meta->srch_cnt = le64toh(*(uint64_t *) buf);
	buf += sizeof(uint64_t);
	deps_len = le64toh(*(uint64_t *) buf);
	buf += sizeof(uint64_t);
	*pos += 2 * sizeof(Rid) + 2 * sizeof(uint64_t);

	if (gap_init(&meta->deps, deps_len) < 0)
		return -1;

        gap_write(&meta->deps, buf, deps_len);
	*pos += deps_len;
	meta->changed = 0;
	return 0;
}

